/**
 * VYPe11 Project
 * VYPe11 language compiler.
 * Authors: Jozef Lang (xlangj01), 
 * 			Zoltan Zemko (xzemko01)
 * 2011
 */
package cz.vutbr.fit.VYPe.team18.VYPe11.sematic.declared;

import java.util.LinkedList;

import cz.vutbr.fit.VYPe.team18.VYPe11.exception.SematicException;
import cz.vutbr.fit.VYPe.team18.VYPe11.nodes.IdentifierNode;
import cz.vutbr.fit.VYPe.team18.VYPe11.nodes.data_type.DataType;
import cz.vutbr.fit.VYPe.team18.VYPe11.nodes.expression.terminal.FunctionInvocationNode;
import cz.vutbr.fit.VYPe.team18.VYPe11.nodes.statement.VariableDeclarationStatementNode;

public class DeclarationLevels extends LinkedList<LinkedList<DeclaredItem>> {
	boolean mainDeclared=false;
	
	public DeclarationLevels() {
		levelUp();
	}
	
	public void levelUp() {
		this.add(new LinkedList<DeclaredItem>());
	}
	
	public void levelDown()
	{
		int n=this.size();
		if(n>1)	// 1. level sa nemaze - tam su globalne funkcie, premenne :P
			this.removeLast();
	}
	
	public void addVariablesByStatement(VariableDeclarationStatementNode statement) {
		DataType type=statement.getTypeNode().getType();
		
		for (IdentifierNode idNode : statement.getIdNode_list()) {
			this.getLast().add(new Variable(type, idNode.getId()));
		}
	}	
	
	public void addDeclaredItem(DeclaredItem item)
	{
		this.getLast().add(item);
	}
	
	
	public Variable findVariableById(String id) throws SematicException {
		// predpokladam, ze nebudu globalne premenne a lokalne funkcie !!!
		for(int i=this.size(); i>1; i--) {
			LinkedList<DeclaredItem> decItems = this.get(i-1);
			for(int j=decItems.size(); j>0; j--) {
				Variable var=(Variable)decItems.get(j-1);
				if(id.equals(var.getId()))
					return var;
			}
		}
		
		// overime, ci nie je to parameter funkcie
		Function fun=(Function)this.getFirst().getLast();
		for (Variable var : fun.getArgumentList()) {
			if(id.equals(var.getId()))
				return var;
		} 
		
		throw new SematicException("Identifier "+id+" is not declared.\n");
	}
	
	public Function findFunctionByFunctionInvocationNode(FunctionInvocationNode node) throws SematicException {
		// find inner function
		Function function = new Function(node);
		Function fun=findFunction(function);
		if(fun==null)
			throw new SematicException("Function "+node.getId().getId()+"() is not declared.\n");				
		return fun;
	}
		
	public Function findFunctionByFunctionInvocationNode1(FunctionInvocationNode node) throws SematicException {
		Function fun=findFunction(new Function(node));
		if(fun==null)
			throw new SematicException("Function "+node.getId().getId()+"() is not declared.\n");		
		return fun;
	}
	
	public Function findFunction(Function function) throws SematicException {
		// find inner function
		Function fun = findInnerFunction(function);
		if(fun!=null)
			return fun;
		
		// find declared function
		LinkedList<DeclaredItem> list=this.get(0);
		for (DeclaredItem declaredItem : list) {
			// predpokladam, ze nebudu globalne premenne a lokalne funkcie !!!
			fun=(Function)declaredItem;
			
			if(fun.getId().equals(function.getId())
				&& function.getArgumentList().size()==fun.getArgumentList().size())
			{	// overenie typov
				int i;
				for (i=0; i<function.getArgumentList().size();i++) 
				{
					DataType fdt=fun.getArgumentList().get(i).getType();
					DataType edt=function.getArgumentList().get(i).getType();
					if(fdt!=edt)
						break;
				}

				// nasla sa deklaracia funkcie
				if(i==fun.getArgumentList().size()) {
					return fun;
				}
			}
		}
		return null;		
	}
		
	private Function findInnerFunction(Function function) throws SematicException {
		String id=function.getId();
		if(id.equals("print")) 
		{
			return new Function(DataType.VOID, id, function.getArgumentList());
		}
		else if(id.equals("read_char")) 
		{
			if(function.getArgumentList().size()==0)
				return new Function(DataType.CHAR, id, function.getArgumentList());
		}
		else if(id.equals("read_short")) 
		{
			if(function.getArgumentList().size()==0)
				return new Function(DataType.SHORT, id, function.getArgumentList());
		}
		else if(id.equals("read_int")) 
		{
			if(function.getArgumentList().size()==0)
				return new Function(DataType.INT, id, function.getArgumentList());
		}
		else if(id.equals("read_string"))
		{
			if(function.getArgumentList().size()==0)
				return new Function(DataType.STRING, id, function.getArgumentList());
		}
		else if(id.equals("strcat"))
		{
			if(function.getArgumentList().size()==2)
			{
				LinkedList<Variable> list=function.getArgumentList();
				if(list.get(0).getType()==DataType.STRING
					&& list.get(1).getType()==DataType.STRING)
				{
					return new Function(DataType.STRING, id, function.getArgumentList());
				}
			}
		}
		return null;
	}
	
	public void verifyDeclaredFunctions() throws SematicException {
		boolean mainExist=false;
		
		for (DeclaredItem item : this.get(0)) {
			Function fun=(Function)item;
			if(!fun.getIsDefined()) {
				throw new SematicException("Function "+fun.getId()+"() is declared but not defined.\n");
			}
			if(fun.getId().equals("main") && fun.getArgumentList().size()==0)
				mainExist=true;
		}
		if(!mainExist)
			throw new SematicException("Function main(void) is not defined.\n");			
	}
}
